﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace AnaxibiaStudios.Smug
{
  public interface IPrepared { History History { get; set; } }
  public interface IPreparedNoCount<TSignature, TReturn> : IPreparedDetour<IPrepared, TSignature, TReturn>, IPrepared { }
  public interface IPreparedNoCount<TSignature> : IPreparedDetour<IPrepared, TSignature>, IPrepared { }
  public interface IPreparedNoDetour<TSignature> : IPreparedCount<IPrepared, TSignature>, IPrepared { }

  public interface IPreparedDetour<TPrepared, TSignature> : IPrepared
  {
    TPrepared Throw<TException>();
    TPrepared ReplaceWith(TSignature detour);

    TPrepared Real();
    TPrepared Real(Action notify);
  }

  public interface IPreparedDetour<TPrepared, TSignature, TReturn> : IPreparedDetour<TPrepared, TSignature>, IPrepared
  {
    TPrepared Returns(TReturn ret);
  }

  public interface IPreparedCount<TPrepared, TSignature> : IPrepared
  {
    TPrepared CallCount(int count);
    TPrepared CallCount(int count, CountRule rule);
    TPrepared CalledOnce();
    TPrepared CalledAtLeastOnce();
  }

  public class PreparedBase<TDetourReturn, TSignature> :
    IPreparedDetour<IPreparedNoDetour<TSignature>, TSignature>,
    IPreparedCount<TDetourReturn, TSignature>,
    IPrepared
    where TDetourReturn : IPrepared
  {
    //detour
    public IPreparedNoDetour<TSignature> Throw<TException>() { return (IPreparedNoDetour<TSignature>)this; }
    public IPreparedNoDetour<TSignature> ReplaceWith(TSignature detour) { return (IPreparedNoDetour<TSignature>)this; }
    public IPreparedNoDetour<TSignature> Real() { return (IPreparedNoDetour<TSignature>)this; }
    public IPreparedNoDetour<TSignature> Real(Action notify) { return (IPreparedNoDetour<TSignature>)this; }

    //count
    public TDetourReturn CallCount(int count) { return (TDetourReturn)((IPrepared)this); }
    public TDetourReturn CallCount(int count, CountRule rule) { return (TDetourReturn)((IPrepared)this); }
    public TDetourReturn CalledOnce() { return (TDetourReturn)((IPrepared)this); }
    public TDetourReturn CalledAtLeastOnce() { return (TDetourReturn)((IPrepared)this); }

    public History History { get; set; }
  }

  public class Prepared<TSignature> :
    PreparedBase<IPreparedNoCount<TSignature>, TSignature>,
    IPreparedNoCount<TSignature>,
    IPreparedNoDetour<TSignature>
  {
    IPrepared IPreparedDetour<IPrepared, TSignature>.Throw<TException>() { return Throw<TException>(); }
    IPrepared IPreparedDetour<IPrepared, TSignature>.ReplaceWith(TSignature detour) { return ReplaceWith(detour); }
    IPrepared IPreparedDetour<IPrepared, TSignature>.Real() { return Real(); }
    IPrepared IPreparedDetour<IPrepared, TSignature>.Real(Action notify) { return Real(notify); }

    IPrepared IPreparedCount<IPrepared, TSignature>.CallCount(int count) { return CallCount(count); }
    IPrepared IPreparedCount<IPrepared, TSignature>.CallCount(int count, CountRule rule) { return CallCount(count, rule); }
    IPrepared IPreparedCount<IPrepared, TSignature>.CalledOnce() { return CalledOnce(); }
    IPrepared IPreparedCount<IPrepared, TSignature>.CalledAtLeastOnce() { return CalledAtLeastOnce(); }
  }

  public class Prepared<TSignature, TReturn> :
    PreparedBase<IPreparedNoCount<TSignature, TReturn>, TSignature>,
    IPreparedDetour<IPreparedNoDetour<TSignature>, TSignature, TReturn>,
    IPreparedNoCount<TSignature, TReturn>,
    IPreparedNoDetour<TSignature>
  {
    //detour
    public IPreparedNoDetour<TSignature> Returns(TReturn ret) { return (IPreparedNoDetour<TSignature>)this; }

    IPrepared IPreparedDetour<IPrepared, TSignature, TReturn>.Returns(TReturn ret) { return Returns(ret); }
    IPrepared IPreparedDetour<IPrepared, TSignature>.Throw<TException>() { return Throw<TException>(); }
    IPrepared IPreparedDetour<IPrepared, TSignature>.ReplaceWith(TSignature detour) { return ReplaceWith(detour); }
    IPrepared IPreparedDetour<IPrepared, TSignature>.Real() { return Real(); }
    IPrepared IPreparedDetour<IPrepared, TSignature>.Real(Action notify) { return Real(notify); }

    IPrepared IPreparedCount<IPrepared, TSignature>.CallCount(int count) { return CallCount(count); }
    IPrepared IPreparedCount<IPrepared, TSignature>.CallCount(int count, CountRule rule) { return CallCount(count, rule); }
    IPrepared IPreparedCount<IPrepared, TSignature>.CalledOnce() { return CalledOnce(); }
    IPrepared IPreparedCount<IPrepared, TSignature>.CalledAtLeastOnce() { return CalledAtLeastOnce(); }
  }

  public static class Prepare
  {
    public static Advanced.PreparedEvent<TSignature> Event<TSignature>(object proxy, Action<TSignature> eventSnatcher) { throw new NotImplementedException(); }
    public static Advanced.PreparedEvent<TSignature> Event<TSignature>(object proxy, Func<TSignature, string> eventSnatcher) { throw new NotImplementedException(); }

    public static Advanced.PreparedEvent<Delegate> Event(object proxy, Action eventSnatcher) { throw new NotImplementedException(); }
    public static Advanced.PreparedEvent<Delegate> Event(object proxy, Func<string> eventSnatcher) { throw new NotImplementedException(); }

    public static Prepared<Func<TReturn>, TReturn> Property<TReturn>(Expression<Func<TReturn>> getMethod) { return new Prepared<Func<TReturn>, TReturn>(); }
    public static Prepared<Action<T1>> Property<T1>(Action<T1> snatchSet) { return new Prepared<Action<T1>>(); }

    public static Prepared<Action> Action(object proxy, string methodName) { return new Prepared<Action>(); }
    public static Prepared<Action> Action(Action method) { return new Prepared<Action>(); }
    public static Prepared<Action> Action(Action method, ReplayRule rule) { return new Prepared<Action>(); }

    public static Prepared<Func<TReturn>, TReturn> Func<TReturn>(Func<TReturn> method) { return new Prepared<Func<TReturn>, TReturn>(); }
    public static Prepared<Func<TReturn>, TReturn> Func<TReturn>(Func<TReturn> method, ReplayRule rule) { return new Prepared<Func<TReturn>, TReturn>(); }
    public static Prepared<Func<T1, TReturn>, TReturn> Func<T1, TReturn>(Func<T1, TReturn> method) { return new Prepared<Func<T1, TReturn>, TReturn>(); }
    public static Prepared<Func<T1, TReturn>, TReturn> Func<T1, TReturn>(Func<T1, TReturn> method, ReplayRule rule) { return new Prepared<Func<T1, TReturn>, TReturn>(); }

    public static Prepared<TDelegate> Method<TDelegate>(TDelegate method) { return new Prepared<TDelegate>(); }    

    public static Prepared<Func<TReturn>, TReturn> Call<TReturn>(Expression<Func<TReturn>> expression, ReplayRule rule) { return Call(expression); }
    public static Prepared<Func<TReturn>, TReturn> Call<TReturn>(Expression<Func<TReturn>> expression)
    {
      var body = expression.Body;
      var bodyType = body.GetType();
      var methodProp = bodyType.GetProperty("Method");
      var getter = methodProp.GetGetMethod();
      var methodInfo = getter.Invoke(body, new object[] { }) as MethodInfo;

      return new Prepared<Func<TReturn>, TReturn>();
    }
  }
}
